手写 Promise

const PENDING = "pending";
const RESOLVED = "resolved";
const REJECTED = "rejected";

function MyPromise(fn) {
  const that = this;
  that.state = PENDING;
  that.value = null;
  // `resolvedCallbacks` 和 `rejectedCallbacks` 用于保存 `then` 中的回调
  // 因为当执行完`Promise` 时状态可能还是等待中,这时候应该把`then` 中的回调保存起来用于状态改变时使用
  that.resolvedCallbacks = [];
  that.rejectedCallbacks = [];

  function resolve(value) {
    // 对于 `resolve` 函数来说,首先需要判断传入的值是否为 `Promise` 类型
    if (value instanceof MyPromise) {
      return value.then(resolve, reject);
    }
    // 为了保证函数执行顺序,需要将两个函数体代码使用 `setTimeout` 包裹起来
    setTimeout(() => {
      if (that.state === PENDING) {
        that.state = RESOLVED;
        that.value = value;
        that.resolvedCallbacks.map((cb) => cb(that.value));
      }
    }, 0);
  }
  function reject(value) {
    setTimeout(() => {
      if (that.state === PENDING) {
        that.state = REJECTED;
        that.value = value;
        that.rejectedCallbacks.map((cb) => cb(that.value));
      }
    }, 0);
  }

  try {
    fn(resolve, reject);
  } catch (e) {
    reject(e);
  }
}

MyPromise.prototype.then = function (onFulfilled, onRejected) {
  const that = this;
  onFulfilled = typeof onFulfilled === "function" ? onFulfilled : (v) => v;
  onRejected =
    typeof onRejected === "function"
      ? onRejected
      : (r) => {
          throw r;
        };
  if (that.state === state.padding) {
    that.resolvedCallbacks.push(onFulfilled);
    that.rejectedCallbacks.push(onRejected);
  }
  if (that.state === state.resolved) {
    onFulfilled(that.value);
  }
  if (that.state === state.rejected) {
    onRejected(that.value);
  }
};

实现一个符合 Promise/A+ 规范的 Promise

接下来继续改造 then 函数中的代码,首先我们需要新增一个变量 promise2,因为每个 then 函数都需要返回一个新的 Promise 对象,该变量用于保存新的返回对象,然后我们先来改造判断等待态的逻辑

if (that.state === PENDING) {
  return (promise2 = new MyPromise((resolve, reject) => {
    that.resolvedCallbacks.push(() => {
      try {
        const x = onFulfilled(that.value);
        resolutionProcedure(promise2, x, resolve, reject);
      } catch (r) {
        reject(r);
      }
    });

    that.rejectedCallbacks.push(() => {
      try {
        const x = onRejected(that.value);
        resolutionProcedure(promise2, x, resolve, reject);
      } catch (r) {
        reject(r);
      }
    });
  }));
}
  • 首先我们返回了一个新的 Promise 对象,并在 Promise 中传入了一个函数
  • 函数的基本逻辑还是和之前一样,往回调数组中 push 函数
  • 同样,在执行函数的过程中可能会遇到错误,所以使用了 try...catch 包裹
  • 规范规定,执行 onFulfilled 或者 onRejected 函数时会返回一个 x,并且执行 Promise 解决过程,这是为了不同的 Promise 都可以兼容使用,比如 JQuery 的 Promise 能兼容 ES6 的 Promise

接下来我们改造判断执行态的逻辑

if (that.state === RESOLVED) {
  return (promise2 = new MyPromise((resolve, reject) => {
    setTimeout(() => {
      try {
        const x = onFulfilled(that.value);
        resolutionProcedure(promise2, x, resolve, reject);
      } catch (reason) {
        reject(reason);
      }
    });
  }));
}
  • 其实大家可以发现这段代码和判断等待态的逻辑基本一致,无非是传入的函数的函数体需要异步执行,这也是规范规定的
  • 对于判断拒绝态的逻辑这里就不一一赘述了,留给大家自己完成这个作业

最后,当然也是最难的一部分,也就是实现兼容多种 PromiseresolutionProcedure 函数

function resolutionProcedure(promise2, x, resolve, reject) {
  if (promise2 === x) {
    return reject(new TypeError("Error"));
  }
}

首先规范规定了 x 不能与 promise2 相等,这样会发生循环引用的问题,比如如下代码

let p = new MyPromise((resolve, reject) => {
  resolve(1);
});
let p1 = p.then((value) => {
  return p1;
});

然后需要判断 x 的类型

if (x instanceof MyPromise) {
  x.then(function (value) {
    resolutionProcedure(promise2, value, resolve, reject);
  }, reject);
}

这里的代码是完全按照规范实现的。如果 xPromise 的话,需要判断以下几个情况:

  1. 如果 x 处于等待态,Promise 需保持为等待态直至 x 被执行或拒绝
  2. 如果 x 处于其他状态,则用相同的值处理 Promise

当然以上这些是规范需要我们判断的情况,实际上我们不判断状态也是可行的。

接下来我们继续按照规范来实现剩余的代码

let called = false;
if (x !== null && (typeof x === "object" || typeof x === "function")) {
  try {
    let then = x.then;
    if (typeof then === "function") {
      then.call(
        x,
        (y) => {
          if (called) return;
          called = true;
          resolutionProcedure(promise2, y, resolve, reject);
        },
        (e) => {
          if (called) return;
          called = true;
          reject(e);
        }
      );
    } else {
      resolve(x);
    }
  } catch (e) {
    if (called) return;
    called = true;
    reject(e);
  }
} else {
  resolve(x);
}
  • 首先创建一个变量 called 用于判断是否已经调用过函数
  • 然后判断 x 是否为对象或者函数,如果都不是的话,将 x 传入 resolve
  • 如果 x 是对象或者函数的话,先把 x.then 赋值给 then,然后判断 then 的类型,如果不是函数类型的话,就将 x 传入 resolve
  • 如果 then 是函数类型的话,就将 x 作为函数的作用域 this 调用之,并且传递两个回调函数作为参数,第一个参数叫做 resolvePromise ,第二个参数叫做 rejectPromise,两个回调函数都需要判断是否已经执行过函数,然后进行相应的逻辑
  • 以上代码在执行的过程中如果抛错了,将错误传入 reject 函数中

Promise/A+ 规范

遵循下面几个规则:

  • 如果可选参数不为函数时应该被忽略;
  • 两个函数都应该是异步执行的,即放入事件队列等待下一轮 tick,而非立即执行;
  • 当调用 onFulfilled 函数时,会将当前 Promise 的值作为参数传入;
  • 当调用 onRejected 函数时,会将当前 Promise 的失败原因作为参数传入;
  • then 函数的返回值为 Promise。

Promise 状态

Promise 的 3 个状态分别为 pending、fulfilled 和 rejected。

Promise 解决过程

Promise 解决过程是一个抽象的操作,即接收一个 promise 和一个值 x,目的就是对 Promise 形式的执行结果进行统一处理。需要考虑以下 3 种情况。

情况 1: x 等于 promise

抛出一个 TypeError 错误,拒绝 promise。

情况 2:x 为 Promise 的实例

如果 x 处于等待状态,那么 promise 继续等待至 x 执行或拒绝,否则根据 x 的状态执行/拒绝 promise。

情况 3:x 为对象或函数

该情况的核心是取出 x.then 并调用,在调用的时候将 this 指向 x。将 then 回调函数中得到结果 y 传入新的 Promise 解决过程中,形成一个递归调用。其中,如果执行报错,则以对应的错误为原因拒绝 promise。

这一步是处理拥有 then() 函数的对象或函数,这类对象或函数我们称之为“thenable”。注意,它只是拥有 then() 函数,并不是 Promise 实例。

情况 4:如果 x 不为对象或函数

以 x 作为值,执行 promise。

Promise 实现

Promise 是单次执行的,所以需要判断状态为 PENDING 的时候再执行函数 resolve() 或函数 reject() 。同时 Promise 的内部异常不能直接抛出,所以要进行异常捕获。

then() 被调用时应该返回一个新的 Promise,所以在上面的 3 种状态的处理逻辑中,都应该创建并返回一个 Promise 实例。以执行状态为例,可以改成下面的样子。

case FULFILLED:
  promise = new Promise(function (resolve, reject) {
    setTimeout(function () {
      try {
        onFulfilled(self.value);
      } catch (e) {
        reject(e)
      }
    })
  });
  break;

同时,它带来的另一个效果是支持链式调用。在链式调用的情况下,如果 Promise 实例处于等待状态,那么需要保存多个 resolve() 或 reject() 函数,所以 onFulfilledFn 和 onRejectedFn 应该改成数组。

case PENDING:
  promise = new Promise(function (resolve, reject) {
    self.onFulfilledFn.push(function () {
      try {
        onFulfilled(self.value);
      } catch (e) {
        reject(e)
      }
    });
    self.onRejectedFn.push(function () {
      try {
        onRejected(self.reason);
      } catch (e) {
        reject(e)
      }
    })
  });
  break;

对应的,Promise 构造函数中应该初始化属性 onFulfilledFn 和 onRejectedFn 为数组,同时 resolve() 和 reject() 函数在改变状态时应该调用这个数组中的函数,并且这个调用过程应该是异步的。

function Promise(execute) {
  // ...
  self.onFulfilledFn = [];
  self.onRejectedFn = [];
  // ...
  function resolve(value) {
    setTimeout(function () {
      // ...
      self.onFulfilledFn.forEach(function (f) {
        f(self.value);
      });
    });
  }
  function reject(reason) {
    setTimeout(function () {
      // ...
      self.onRejectedFn.forEach(function (f) {
        f(self.reason);
      });
    });
  }
}

resolvePromise() 函数

前面提到解决过程函数有两个参数及 3 种情况,先来考虑第 1 种情况,promise 与 x 相等,应该直接抛出 TypeError 错误:

function resolvePromise(promise, x) {
  if (promise === x) {
    return reject(new TypeError("x 不能与 promise 相等"));
  }
}

情况 2,x 为 Promise 的实例,应该尝试让 promise 接受 x 的状态,怎么接受呢?

直接改变 promise 状态肯定是不可取的,首先状态信息属于内部变量,其次也无法调用属性 onResolvedFn 和 onFulfilledFn 中的待执行函数。所以必须要通过调用 promise 在构造时的函数 resolve() 和 reject() 来改变。

如果 x 处于等待状态,那么 promise 继续保持等待状态,等待解决过程函数 resolvePromise() 执行,否则应该用相同的值执行或拒绝 promise。我们无法从外部拒绝或执行一个 Promise 实例,只能通过调用构造函数传入的 resolve() 和 reject() 函数来实现。所以还需要把这两个函数作为参数传递到 resolvePromise 函数中。

在函数 resolvePromise() 内部加上情况 2 的判断,代码如下:

function resolvePromise(promise, x, resolve, reject) {
  ...
  if (x instanceof Promise) {
    if (x.state === FULFILLED) {
      resolve(x.value)
    } else if (x.state === REJECTED) {
      reject(x.reason)
    } else {
      x.then(function (y) {
        resolvePromise(promise, y, resolve, reject)
      }, reject)
    }
  }
}

再来实现情况 3,将 x.then 取出然后执行,并将执行结果放入解决过程函数 resolvePromise() 中。 考虑到 x 可能只是一个 thenable 而非真正的 Promise,所以在调用 then() 函数的时候要设置一个变量 excuted 避免重复调用。同时记得在执行时添加异常捕获并及时拒绝当前 promise。

if (x !== null && (typeof x === "object" || typeof x === "function")) {
  var executed;
  try {
    var then = x.then;
    if (typeof then === "function") {
      then.call(
        x,
        function (y) {
          if (executed) return;
          executed = true;
          return resolvePromise(promise, y, resolve, reject);
        },
        function (e) {
          if (executed) return;
          executed = true;
          reject(e);
        }
      );
    } else {
      resolve(x);
    }
  } catch (e) {
    if (executed) return;
    executed = true;
    reject(e);
  }
}

情况 4 就很简单了,直接把 x 作为值执行。

resolve(x);

手写 Promise 实现

// 三种状态
const PENDING = "pending";
const RESOLVED = "resolved";
const REJECTED = "rejected";
// promise 接收一个函数参数,该函数会立即执行
function Promise(fn) {
  let that = this;
  that.currentState = PENDING;
  that.value = undefined;
  // 用于保存 then 中的回调,只有当 promise
  // 状态为 pending 时才会缓存,并且每个实例至多缓存一个
  that.resolvedCallbacks = [];
  that.rejectedCallbacks = [];

  that.resolve = function (value) {
    if (value instanceof Promise) {
      // 如果 value 是个 Promise,递归执行
      return value.then(that.resolve, that.reject);
    }
    setTimeout(() => {
      // 异步执行,保证执行顺序
      if (that.currentState === PENDING) {
        that.currentState = RESOLVED;
        that.value = value;
        that.resolvedCallbacks.forEach((cb) => cb());
      }
    });
  };

  that.reject = function (reason) {
    setTimeout(() => {
      // 异步执行,保证执行顺序
      if (that.currentState === PENDING) {
        that.currentState = REJECTED;
        that.value = reason;
        that.rejectedCallbacks.forEach((cb) => cb());
      }
    });
  };
  // 用于解决以下问题
  // new Promise(() => throw Error('error))
  try {
    fn(that.resolve, that.reject);
  } catch (e) {
    that.reject(e);
  }
}

Promise.prototype.then = function (onResolved, onRejected) {
  var self = this;
  // 规范 2.2.7,then 必须返回一个新的 promise
  var promise2;
  // 规范 2.2.onResolved 和 onRejected 都为可选参数
  // 如果类型不是函数需要忽略,同时也实现了透传
  // Promise.resolve(4).then().then((value) => console.log(value))
  onResolved = typeof onResolved === "function" ? onResolved : (v) => v;
  onRejected =
    typeof onRejected === "function"
      ? onRejected
      : (r) => {
          throw r;
        };

  if (self.currentState === RESOLVED) {
    return (promise2 = new Promise(function (resolve, reject) {
      // 规范 2.2.4,保证 onFulfilled,onRejected 异步执行
      // 所以用了 setTimeout 包裹下
      setTimeout(function () {
        try {
          var x = onResolved(self.value);
          resolutionProcedure(promise2, x, resolve, reject);
        } catch (reason) {
          reject(reason);
        }
      });
    }));
  }

  if (self.currentState === REJECTED) {
    return (promise2 = new Promise(function (resolve, reject) {
      setTimeout(function () {
        // 异步执行onRejected
        try {
          var x = onRejected(self.value);
          resolutionProcedure(promise2, x, resolve, reject);
        } catch (reason) {
          reject(reason);
        }
      });
    }));
  }

  if (self.currentState === PENDING) {
    return (promise2 = new Promise(function (resolve, reject) {
      self.resolvedCallbacks.push(function () {
        // 考虑到可能会有报错,所以使用 try/catch 包裹
        try {
          var x = onResolved(self.value);
          resolutionProcedure(promise2, x, resolve, reject);
        } catch (r) {
          reject(r);
        }
      });

      self.rejectedCallbacks.push(function () {
        try {
          var x = onRejected(self.value);
          resolutionProcedure(promise2, x, resolve, reject);
        } catch (r) {
          reject(r);
        }
      });
    }));
  }
};
// 规范 2.3
function resolutionProcedure(promise2, x, resolve, reject) {
  // 规范 2.3.1,x 不能和 promise2 相同,避免循环引用
  if (promise2 === x) {
    return reject(new TypeError("Error"));
  }
  // 规范 2.3.2
  // 如果 x 为 Promise,状态为 pending 需要继续等待否则执行
  if (x instanceof Promise) {
    if (x.currentState === PENDING) {
      x.then(function (value) {
        // 再次调用该函数是为了确认 x resolve 的
        // 参数是什么类型,如果是基本类型就再次 resolve
        // 把值传给下个 then
        resolutionProcedure(promise2, value, resolve, reject);
      }, reject);
    } else {
      x.then(resolve, reject);
    }
    return;
  }
  // 规范 2.3.3.3.3
  // reject 或者 resolve 其中一个执行过得话,忽略其他的
  let called = false;
  // 规范 2.3.3,判断 x 是否为对象或者函数
  if (x !== null && (typeof x === "object" || typeof x === "function")) {
    // 规范 2.3.3.2,如果不能取出 then,就 reject
    try {
      // 规范 2.3.3.1
      let then = x.then;
      // 如果 then 是函数,调用 x.then
      if (typeof then === "function") {
        // 规范 2.3.3.3
        then.call(
          x,
          (y) => {
            if (called) return;
            called = true;
            // 规范 2.3.3.3.1
            resolutionProcedure(promise2, y, resolve, reject);
          },
          (e) => {
            if (called) return;
            called = true;
            reject(e);
          }
        );
      } else {
        // 规范 2.3.3.4
        resolve(x);
      }
    } catch (e) {
      if (called) return;
      called = true;
      reject(e);
    }
  } else {
    // 规范 2.3.4,x 为基本类型
    resolve(x);
  }
}

/**
 * Promise.all Promise进行并行处理
 * 参数: promise对象组成的数组作为参数
 * 返回值: 返回一个Promise实例
 * 当这个数组里的所有promise对象全部变为resolve状态的时候,才会resolve。
 */
Promise.all = function (promises) {
  return new Promise((resolve, reject) => {
    let done = gen(promises.length, resolve);
    promises.forEach((promise, index) => {
      promise.then((value) => {
        done(index, value);
      }, reject);
    });
  });
};

function gen(length, resolve) {
  let count = 0;
  let values = [];
  return function (i, value) {
    values[i] = value;
    if (++count === length) {
      console.log(values);
      resolve(values);
    }
  };
}

/**
 * Promise.race
 * 参数: 接收 promise对象组成的数组作为参数
 * 返回值: 返回一个Promise实例
 * 只要有一个promise对象进入 FulFilled 或者 Rejected 状态的话,就会继续进行后面的处理(取决于哪一个更快)
 */
Promise.race = function (promises) {
  return new Promise((resolve, reject) => {
    promises.forEach((promise, index) => {
      promise.then(resolve, reject);
    });
  });
};

// 用于promise方法链时 捕获前面onFulfilled/onRejected抛出的异常
Promise.prototype.catch = function (onRejected) {
  return this.then(null, onRejected);
};

Promise.resolve = function (value) {
  return new Promise((resolve) => {
    resolve(value);
  });
};

Promise.reject = function (reason) {
  return new Promise((resolve, reject) => {
    reject(reason);
  });
};

链式调用实现

光是实现了异步操作可不行,我们常常用到 new Promise().then().then() 这样的链式调用来解决回调地狱。

规范如何定义 then 方法:

  • 每个 then 方法都返回一个新的 Promise 对象(原理的核心)
  • 如果 then 方法中显示地返回了一个 Promise 对象就以此对象为准,返回它的结果
  • 如果 then 方法中返回的是一个普通值(如 Number、String 等)就使用此值包装成一个新的 Promise 对象返回。
  • 如果 then 方法中没有 return 语句,就视为返回一个用 Undefined 包装的 Promise 对象
  • 若 then 方法中出现异常,则调用失败态方法(reject)跳转到下一个 then 的 onRejected
  • 如果 then 方法没有传入任何回调,则继续向下传递(值的传递特性) 总的来说就是不论何时 then 方法都要返回一个 Promise,这样才能调用下一个 then 方法。

实现 Promise 的常用 API

实现 Promise.finally

它就是一个语法糖,在当前 promise 实例执行完 then 或者 catch 后,均会触发。Promise.prototype.finally 的执行与 promise 实例的状态无关,不依赖于 promise 的执行后返回的结果值。其传入的参数是函数对象。 实现思路:

  • 考虑到 promise 的 resolver 可能是个异步函数,因此 finally 实现中,要通过调用实例上的 then 方法,添加 callback 逻辑
  • 成功透传 value,失败透传 error
Promise.prototype.finally = function (cb) {
  return this.then(
    (value) => Promise.resolve(cb()).then(() => value),
    (error) =>
      Promise.resolve(cb()).then(() => {
        throw error;
      })
  );
};

实现 Promise.all

Promise.all(iterators)返回一个新的 Promise 实例。iterators 中包含外界传入的多个 promise 实例。

对于返回的新的 Promise 实例,有以下两种情况:

  • 如果传入的所有 promise 实例的状态均变为fulfilled,那么返回的 promise 实例的状态就是fulfilled,并且其 value 是 传入的所有 promise 的 value 组成的数组。
  • 如果有一个 promise 实例状态变为了rejected,那么返回的 promise 实例的状态立即变为rejected

实现思路:

  • 传入的参数不一定是数组对象,可以是"遍历器"
  • 传入的每个实例不一定是 promise,需要用Promise.resolve()包装
  • 借助"计数器",标记是否所有的实例状态均变为fulfilled
Promise.all = function (iterators) {
  const promises = Array.from(iterators);
  const num = promises.length;
  const resolvedList = new Array(num);
  let resolvedNum = 0;

  return new Promise((resolve, reject) => {
    promises.forEach((promise, index) => {
      Promise.resolve(promise)
        .then((value) => {
          // 保存这个 promise 实例的 value
          resolvedList[index] = value;
          // 通过计数器,标记是否所有实例均 fulfilled
          if (++resolvedNum === num) {
            resolve(resolvedList);
          }
        })
        .catch(reject);
    });
  });
};

实现 Promise.race

在代码实现前,我们需要先了解 Promise.race 的特点:

  1. Promise.race 返回的仍然是一个 Promise. 它的状态与第一个完成的 Promise 的状态相同。它可以是完成( resolves),也可以是失败(rejects),这要取决于第一个 Promise 是哪一种状态。
  2. 如果传入的参数是不可迭代的,那么将会抛出错误。
  3. 如果传的参数数组是空,那么返回的 promise 将永远等待。
  4. 如果迭代包含一个或多个非承诺值和/或已解决/拒绝的承诺,则 Promise.race 将解析为迭代中找到的第一个值。
Promise.race = function (promises) {
  // promises 必须是一个可遍历的数据结构,否则抛错
  return new Promise((resolve, reject) => {
    if (typeof promises[Symbol.iterator] !== "function") {
      //真实不是这个错误
      Promise.reject("args is not iteratable!");
    }
    if (promises.length === 0) {
      return;
    } else {
      for (let i = 0; i < promises.length; i++) {
        Promise.resolve(promises[i]).then(
          (data) => {
            resolve(data);
            return;
          },
          (err) => {
            reject(err);
            return;
          }
        );
      }
    }
  });
};

promise.then(...).catch(...);与 promise.then(..., ...); 并不等价, 尤其注意当 promise.then(...).catch(...); 中的 then 会抛异常的情况下。

Promise.myRace = function (iterators) {
  const promises = Array.from(iterators);

  return new Promise((resolve, reject) => {
    promises.forEach((promise, index) => {
      Promise.resolve(promise).then(resolve).catch(reject);
    });
  });
};
Promise.race = function (promises) {
  return new Promise((resolve, reject) => {
    if (Array.isArray(promises)) {
      if (promises.length === 0) return resolve(promises);
      promises.forEach((item) => {
        Promise.resolve(item).then(
          (value) => resolve(value),
          (reason) => reject(reason)
        );
      });
    } else return reject(new TypeError("Argument is not iterable"));
  });
};

实现 Promise.any

Promise.any(iterators)的传参和返回值与Promise.all相同。

如果传入的实例中,有任一实例变为fulfilled,那么它返回的 promise 实例状态立即变为fulfilled;如果所有实例均变为rejected,那么它返回的 promise 实例状态为rejected

Promise.allPromise.any的关系,类似于,Array.prototype.everyArray.prototype.some的关系。

代码实现

实现思路和Promise.all类似。不过由于对异步过程的处理逻辑不同,因此这里的计数器用来标识是否所有的实例均 rejected

Promise.any = function (iterators) {
  const promises = Array.from(iterators);
  const num = promises.length;
  const rejectedList = new Array(num);
  let rejectedNum = 0;

  return new Promise((resolve, reject) => {
    promises.forEach((promise, index) => {
      Promise.resolve(promise)
        // 只要有一个 promise 执行成功,就返回。后面的 promise 就不管了
        .then((value) => resolve(value))
        .catch((error) => {
          // 所有的 promise 执行失败,才算执行失败,并返回所有错误信息
          rejectedList[index] = error;
          if (++rejectedNum === num) {
            reject(rejectedList);
          }
        });
    });
  });
};

实现 Promise.allSettled

Promise.allSettled(iterators)的传参和返回值与Promise.all相同。

根据ES2020open in new window,此返回的 promise 实例的状态只能是fulfilled。对于传入的所有 promise 实例,会等待每个 promise 实例结束,并且返回规定的数据格式。

如果传入 a、b 两个 promise 实例:a 变为 rejected,错误是 error1;b 变为 fulfilled,value 是 1。那么Promise.allSettled返回的 promise 实例的 value 就是:

[
  { status: "rejected", value: error1 },
  { status: "fulfilled", value: 1 },
];

代码实现

实现中的计数器,用于统计所有传入的 promise 实例。

const formatSettledResult = (success, value) =>
  success
    ? { status: "fulfilled", value }
    : { status: "rejected", reason: value };

Promise.allSettled = function (iterators) {
  const promises = Array.from(iterators);
  const num = promises.length;
  const settledList = new Array(num);
  let settledNum = 0;

  return new Promise((resolve) => {
    promises.forEach((promise, index) => {
      Promise.resolve(promise)
        .then((value) => {
          settledList[index] = formatSettledResult(true, value);
          if (++settledNum === num) {
            resolve(settledList);
          }
        })
        .catch((error) => {
          settledList[index] = formatSettledResult(false, error);
          if (++settledNum === num) {
            resolve(settledList);
          }
        });
    });
  });
};

实现 Promise.retry(promiseFn, times)

可以设置时间间隔和次数。

Promise.retry = function (promiseFn, times = 3) {
  return new Promise(async (resolve, reject) => {
    while (times--) {
      try {
        var ret = await promiseFn();
        resolve(ret);
        break;
      } catch (error) {
        if (!times) reject(error);
      }
    }
  });
};

function getProm() {
  const n = Math.random();
  return new Promise((resolve, reject) => {
    setTimeout(() => (n > 0.9 ? resolve(n) : reject(n)), 1000);
  });
}

Promise.retry(getProm);

实现 Promise.all(list, limit)

控制一下子发出的请求个数。异步请求控制并发 LimitPromise

function PromiseLimit(funcArray, limit = 5) {
  let i = 0;
  // promise 存储
  const result = [];
  // 执行存储
  const executing = [];
  const queue = function () {
    // 边界处理, funcArray 为空数组
    if (i === funcArray.length) return Promise.all(executing);
    // 一个 promise
    const p = funcArray[i++]();
    // 最终返回的 promise 队列
    result.push(p);
    // 在 p 执行结束之后,将 e 从 executing 数组中取出
    const e = p.then(() => executing.splice(executing.indexOf(e), 1));
    // 执行中的 promise 队列
    executing.push(e);
    // 如果个数到了 limit, 先 race 等待执行结束,再执行 queue 添加一个 promise
    if (executing.length >= limit) {
      return Promise.race(executing).then(
        () => queue(),
        (e) => Promise.reject(e)
      );
    }
    // 如果个数还没到,则直接执行 queue() 函数添加 promise
    return Promise.resolve().then(() => queue());
  };
  return queue().then(() => Promise.all(result));
}

如果需要一个 add 函数用于添加 Promise。那么让我们来尝试实现它:

class PromiseLimit {
  constructor(limit) {
    // 定义限制数量
    this.limit = limit;
    // 定义一个队列用于存储 Promise
    this.queue = [];
    // 用于记录目前在进行中的 Promise 数量
    this.pendingCount = 0;
  }

  add(fn) {
    // 需要执行的任务入队列
    this.queue.push(fn);
    this.run();
  }

  run() {
    while (this.pendingCount < this.limit && this.queue.length) {
      // 如果当前进行中的 Promise 没有达到限制并且队列不为空,则选出队头任务执行
      const fn = this.queue.shift();
      // 将进行中的数量加 1
      this.pendingCount++;
      // 这里函数执行 Promise,并且无论成功还是失败,都对当前正在进行的数量减 1,并实现自调用进入下一个 Promise 的执行
      fn().finally(() => {
        this.pendingCount--;
        this.run();
      });
    }
  }
}

实现 Promise.all(list, limit)

  1. js 实现带并发限制的调度器,其实就是使用 promise 限制并发
  2. 实现一个可以控制请求并发数的最高效的发送请求功能。
  3. 手动控制并发请求 fetchWithLimit。 手动控制并发请求 fetchWithLimit 尽量快的实现

控制一下子发出的请求个数。异步请求控制并发 LimitPromise

function PromiseLimit(funcArray, limit = 5) {
  let i = 0;
  // promise 存储
  const result = [];
  // 执行存储
  const executing = [];
  const queue = function () {
    // 边界处理, funcArray 为空数组
    if (i === funcArray.length) return Promise.all(executing);
    // 一个 promise
    const p = funcArray[i++]();
    // 最终返回的 promise 队列
    result.push(p);
    // 在 p 执行结束之后,将 e 从 executing 数组中取出
    const e = p.then(() => executing.splice(executing.indexOf(e), 1));
    // 执行中的 promise 队列
    executing.push(e);
    // 如果个数到了 limit, 先 race 等待执行结束,再执行 queue 添加一个 promise
    if (executing.length >= limit) {
      return Promise.race(executing).then(
        () => queue(),
        (e) => Promise.reject(e)
      );
    }
    // 如果个数还没到,则直接执行 queue() 函数添加 promise
    return Promise.resolve().then(() => queue());
  };
  return queue().then(() => Promise.all(result));
}

实现一个并发限制功能。

function asyncPool(poolLimit, array, iteratorFn) {
  let i = 0;
  const ret = [];
  const executing = [];
  const enqueue = function () {
    if (i === array.length) {
      return Promise.resolve();
    }
    const item = array[i++];
    const p = Promise.resolve().then(() => iteratorFn(item, array));
    ret.push(p);
    const e = p.then(() => executing.splice(executing.indexOf(e), 1));
    executing.push(e);
    let r = Promise.resolve();
    if (executing.length >= poolLimit) {
      r = Promise.race(executing);
    }
    return r.then(() => enqueue());
  };
  return enqueue().then(() => Promise.all(ret));
}

实现 Promise.retry(promiseFn, times)

fetch 兼容超时重传, 可以设置时间间隔和次数。

Promise.retry = function (promiseFn, times = 3) {
  return new Promise(async (resolve, reject) => {
    while (times--) {
      try {
        var ret = await promiseFn();
        resolve(ret);
        break;
      } catch (error) {
        if (!times) reject(error);
      }
    }
  });
};

function getProm() {
  const n = Math.random();
  return new Promise((resolve, reject) => {
    setTimeout(() => (n > 0.9 ? resolve(n) : reject(n)), 1000);
  });
}

Promise.retry(getProm);

用 promise 实现一个请求超时功能

function promiseWithTimeout(url, timeout = 3000) {
  return new Promise((resolve, reject) => {
    fetch(url)
      .then((data) => data.json())
      .then((data) => resolve(data)); // fetch 先得到结果就 resolve
    setTimeout(() => reject(Error("time is out!")), timeout); // 时间到了还没 fetch 到就 reject
  });
}

其他 Promise 相关的手写

顺序发送 4 个请求 a,b,c,d,要求按照顺序输出

顺序发送 4 个请求 a,b,c,d,要求按照顺序输出,即如果先返回 b,则不输出,再返回 a,输出 a,b

function getData(urls) {
  return new Promise((resolve, reject) => {
    const res = [],
      len = urls.length;
    urls.forEach((url, i) => {
      fetch("http://localhost:8080" + url)
        .then((data) => data.json())
        .then((data) => {
          res[i] = { data, printed: false }; // 将数据放入缓存数组
          let flag = true;
          for (let j = 0; j < len && flag; j += 1) {
            if (res[j]) {
              // 如果标志为 j 的有返回值,则继续
              if (!res[j].printed) {
                console.log(res[j].data);
                res[j].printed = true;
                j === len - 1 && resolve(res.map((o) => o.data));
              }
            } else {
              // 无返回值,则跳出
              flag = false;
            }
          }
        }, reject);
    });
  });
}

const listPromise = getData([
  "/data.json",
  "/data2.json",
  "/data3.json",
  "/data4.json",
]);
listPromise.then((res) => console.log(res));

JS 实现一个带并发限制的异步调度器 Scheduler

完善代码中 Scheduler 类,使得以下程序能正确输出。

// 保证同时运行的任务最多有两个。
class Scheduler {
  constructor() {
    this.count = 2;
    this.queue = [];
    this.run = [];
  }

  add(task) {
    // ...
  }
}

const timeout = (time) =>
  new Promise((resolve) => {
    setTimeout(resolve, time);
  });

const scheduler = new Scheduler();
const addTask = (time, order) => {
  scheduler.add(() => timeout(time)).then(() => console.log(order));
};

addTask(1000, "1");
addTask(500, "2");
addTask(300, "3");
addTask(400, "4");
// output: 2 3 1 4

// 一开始,1、2两个任务进入队列
// 500ms时,2完成,输出2,任务3进队
// 800ms时,3完成,输出3,任务4进队
// 1000ms时,1完成,输出1
// 1200ms时,4完成,输出4

别人的解答:

class Scheduler {
  constructor(count) {
    this.count = 2;
    this.queue = [];
    this.run = [];
  }

  add(task) {
    this.queue.push(task);
    return this.schedule();
  }

  schedule() {
    if (this.run.length < this.count && this.queue.length) {
      const task = this.queue.shift();
      const promise = task().then(() => {
        this.run.splice(this.run.indexOf(promise), 1);
      });
      this.run.push(promise);
      return promise;
    } else {
      return Promise.race(this.run).then(() => this.schedule());
    }
  }
}

手写 promisify 的实现

// 首先定一个需要进行 promisify 的函数
function asyncFn(a, b, callback) {
  // 异步操作,使用 setTimeout 模拟
  console.log("异步请求参数", a, b);
  setTimeout(function () {
    callback("异步请求结果");
  }, 3000);
}

// 我们希望调用的方式是
const proxy = promisify(asyncFn);
proxy(11, 22).then((res) => {
  // 此处输出异步函数执行结果
  console.log(res);
});

// 定义一个方法, 需要针对异步方法做封装,所以需要一个入参,既需要promisify的原异步方法
function promisify(asyncFn) {
  // 方法内部我们需要调用asyncFn方法,并传递原始参数,所以需要返回一个方法来接收参数
  return function (...args) {
    // 由于需要接收参数,所以参数我们可以写为...args
    // 我们需要执行异步操作,并返回一个结果,所以返回一个 promise实例
    return new Promise((resolve) => {
      // asyncFn 需要执行一个回调,所以定义一个回调方法
      const callback = function (...args) {
        resolve(args);
      };
      args.push(callback);
      asyncFn.apply(null, args);
    });
  };
}
Last Updated:
Contributors: yiliang114